package org.mspring.platform.dao.query;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.mspring.platform.dao.query.criterion.Criterion;
import org.mspring.platform.dao.query.criterion.Expression;
import org.mspring.platform.dao.query.criterion.MatchMode;
import org.mspring.platform.utils.CamelCaseUtils;
import org.mspring.platform.utils.StringUtils;

/**
 * @author Gao Youbo
 * @since Jan 31, 2012
 */
public class QueryBuilder {

    private Class<? extends Serializable> requiredClass;
    private String alias; // 别名
    private StringBuilder whereStringBuilder = new StringBuilder(); // where
    private StringBuilder queryStringBuilder = new StringBuilder(); // select
    private StringBuilder countStringBuilder = new StringBuilder(); // count
    private Map<String, Object> queryParams = new HashMap<>();
    private Map<String, Object> namedQueryParams = new HashMap<>();
    private Map<String, Object> validQueryParams = new HashMap<>();

    private QueryBuilder(Class<? extends Serializable> requiredClass, String alias) {
        this(requiredClass, alias, null);
    }

    private QueryBuilder(Class<? extends Serializable> requiredClass, String alias, Map<String, Object> queryParams) {
        this.requiredClass = requiredClass;
        this.alias = alias;
        if (queryParams == null) {
            this.queryParams = new HashMap<>();
        } else {
            this.queryParams = queryParams;
        }
        this.start(requiredClass);
    }

    public static QueryBuilder instance(Class<? extends Serializable> requiredClass) {
        String alias = CamelCaseUtils.toCamelCase(requiredClass.getSimpleName());
        return instance(requiredClass, alias);
    }

    public static QueryBuilder instance(Class<? extends Serializable> requiredClass, Map<String, Object> queryParams) {
        String alias = CamelCaseUtils.toCamelCase(requiredClass.getSimpleName());
        return instance(requiredClass, alias, queryParams);
    }

    public static QueryBuilder instance(Class<? extends Serializable> requiredClass, String alias) {
        return new QueryBuilder(requiredClass, alias);
    }

    public static QueryBuilder instance(Class<? extends Serializable> requiredClass, String alias, Map<String, Object> queryParams) {
        return new QueryBuilder(requiredClass, alias, queryParams);
    }

    private QueryBuilder start(Class<? extends Serializable> requiredClass) {
        if (StringUtils.isBlank(alias)) {
            throw new QueryBuilderException("alias不能为空");
        }
        this.requiredClass = requiredClass;
        String name = requiredClass.getName();
        // String alias = CamelCaseUtils.toCamelCase(requiredClass.getSimpleName());
        this.queryStringBuilder.append("SELECT ").append(alias).append(" FROM ").append(name).append(" ").append(alias);
        this.countStringBuilder.append("SELECT COUNT(*) FROM ").append(name).append(" ").append(alias);
        this.whereStringBuilder.append(" WHERE 1 = 1 ");
        return this;
    }

    public String getQueryString() {
        return queryStringBuilder.toString().concat(whereStringBuilder.toString());
    }

    public String getCountString() {
        return countStringBuilder.toString().concat(whereStringBuilder.toString());
    }

    // /////////////////////////////////////////////////////////////
    // build
    // /////////////////////////////////////////////////////////////
    /*
     * ///////////////////////// ////// equal /////////////////////////
     */
    public QueryBuilder buildEqual(String hqlName, String paramKey) {
        return buildEqual(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildEqual(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.equal(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedEqual(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedEqual(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// not equal /////////////////////////
     */
    public QueryBuilder buildNotEqual(String hqlName, String paramKey) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.notEqual(hqlName, paramKey, String.class));
    }

    public QueryBuilder buildNotEqual(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.notEqual(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedNotEqual(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedNotEqual(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// 大于等于 /////////////////////////
     */
    public QueryBuilder buildGE(String hqlName, String paramKey) {
        return buildGE(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildGE(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.ge(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedGE(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedGe(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// 大于 /////////////////////////
     */
    public QueryBuilder buildGT(String hqlName, String paramKey) {
        return buildGT(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildGT(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.gt(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedGT(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedGt(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// 小于等于 /////////////////////////
     */
    public QueryBuilder buildLE(String hqlName, String paramKey) {
        return buildLE(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildLE(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.le(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedLE(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedLe(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// 小于 /////////////////////////
     */
    public QueryBuilder buildLT(String hqlName, String paramKey) {
        return buildLT(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildLT(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.lt(hqlName, paramKey, paramClass));
    }

    public QueryBuilder buildValuedLT(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedLt(hqlName, paramValue));
    }

    /*
     * ///////////////////////// ////// in /////////////////////////
     */
    public QueryBuilder buildIn(String hqlName, String paramKey) {
        return buildIn(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildIn(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.in(hqlName, paramKey, paramClass));
    }

    /*
     * ///////////////////////// ////// like /////////////////////////
     */
    public QueryBuilder buildLike(String hqlName, String paramKey) {
        return buildLike(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildLike(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.like(hqlName, paramKey, MatchMode.ANYWHERE, paramClass));
    }

    public QueryBuilder buildValuedLike(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedLike(hqlName, paramValue, MatchMode.ANYWHERE));
    }

    public QueryBuilder buildLeftPaddingLike(String hqlName, String paramKey) {
        return buildLeftPaddingLike(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildLeftPaddingLike(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.like(hqlName, paramKey, MatchMode.END, paramClass));
    }

    public QueryBuilder buildValuedLeftPaddingLike(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedLike(hqlName, paramValue, MatchMode.END));
    }

    public QueryBuilder buildRightPaddingLike(String hqlName, String paramKey) {
        return buildRightPaddingLike(hqlName, paramKey, String.class);
    }

    public QueryBuilder buildRightPaddingLike(String hqlName, String paramKey, Class<?> paramClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.like(hqlName, paramKey, MatchMode.START, paramClass));
    }

    public QueryBuilder buildValuedRightPaddingLike(String hqlName, Object paramValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedLike(hqlName, paramValue, MatchMode.START));
    }

    /*
     * ///////////////////////// ////// between /////////////////////////
     */
    public QueryBuilder buildBetween(String hqlName, String beginParamKey, String endParamKey) {
        return buildBetween(hqlName, beginParamKey, endParamKey, String.class);
    }

    public QueryBuilder buildBetween(String hqlName, String beginParamKey, String endParamKey, Class<?> convertClass) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.between(hqlName, beginParamKey, endParamKey, convertClass));
    }

    public QueryBuilder buildValuedBetween(String hqlName, Object beginParamValue, Object endParamValue) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.valuedBetween(hqlName, beginParamValue, endParamValue));
    }

    /*
     * ///////////////////////// ////// not null /////////////////////////
     */
    public QueryBuilder buildNotNull(String hqlName) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.notNull(hqlName));
    }

    /*
     * ///////////////////////// ////// is not /////////////////////////
     */
    public QueryBuilder buildIsNull(String hqlName) {
        hqlName = handleHqlName(hqlName);
        return add(Expression.isNull(hqlName));
    }

    public QueryBuilder buildString(String string) {
        this.whereStringBuilder.append(string);
        return this;
    }

    private QueryBuilder buildAnd() {
        this.whereStringBuilder.append(" and ");
        return this;
    }

    public void addValidQueryParam(String key, String value) {
        this.validQueryParams.put(key, value);
    }

    public String getQueryParamsAsString() {
        StringBuilder params = new StringBuilder();
        for (String key : this.validQueryParams.keySet()) {
            Object value = this.validQueryParams.get(key);
            params.append(key).append("=").append(value);
            params.append("&");
        }
        params.append("hasQueried=true");
        return params.toString();
    }

    /**
     * 添加查询条件
     *
     * @param criterion
     * @return
     */
    private QueryBuilder add(Criterion criterion) {
        if (criterion == null) {
            return this;
        }
        String hql = criterion.toHqlString(this);
        if (QueryHelper.isValidHql(hql)) {
            buildAnd();
            buildString(hql);
        }
        return this;
    }

    /**
     * 处理hqlName，自动加上alias
     *
     * @param hqlName
     * @return
     */
    private String handleHqlName(String hqlName) {
        if (StringUtils.isBlank(alias)) {
            throw new QueryBuilderException("alias不能为空");
        }
        return alias.concat(".").concat(hqlName);
    }

    // /////////////////////////////////////////////////////////////
    // get
    // /////////////////////////////////////////////////////////////
    public Class<? extends Serializable> getRequiredClass() {
        return requiredClass;
    }

    public Map<String, Object> getQueryParams() {
        return this.queryParams;
    }

    public Map<String, Object> getValidQueryParams() {
        return this.validQueryParams;
    }

    public Map<String, Object> getNamedQueryParams() {
        return this.namedQueryParams;
    }

    public String getAlias() {
        return alias;
    }

}
